#include "game.h"
#include "stdio.h"
#include "md5/md5.h"
#include <stdio.h>
#include <stdlib.h>
using namespace std;

// Mutex utilisés :
pthread_mutex_t mutexConnectionClient = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex_msg_sockConnectionClient = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_t mutexAjoutClient = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutexSupprimeClient = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_t mutexReceive = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_t mutexSend = PTHREAD_MUTEX_INITIALIZER;




Game::Game(u_short portEcoute, u_short portLecture, u_short portEcouteServeur,
			u_short portEcouteDistant, u_short portEcouteServeurPrincipal, string adresseServeurPrincipal)
{
	this->log("Creation serveur de partie.");

	this->numberGhost = 0;
	this->numberPacman = 0;
	this->numberPlayer = 0;

	this->portEcoute = portEcoute;
	this->portLecture = portLecture;
	this->portEcouteDistant = portEcouteDistant;
	this->portEcouteServeur = portEcouteServeur;
	this->portEcouteServeurPrincipal = portEcouteServeurPrincipal;

	this->jeton = "jeton";

	this->adresseServeurPrincipal = adresseServeurPrincipal;

	this->sockEcouteServeur = this->createTCPSock(this->portEcouteServeur);
	this->sockLecture = this->createUDPSock(this->portLecture);

	listen(this->sockEcouteServeur, 10);
}

Game::~Game()
{
	this->gamerList.clear();
	close(this->sockEcoute);
	close(this->sockLecture);
	this->numberPlayer = 0;
	this->numberPacman = 0;
	this->numberGhost = 0;
	this->jeton = "jeton";
}

char * md5_str(char *p)
{
	md5_state_t state;
	md5_byte_t digest[16];
	char *hex_output = (char *)malloc(33);
	int di;

	md5_init(&state);
	md5_append(&state, (const md5_byte_t *)p, (int)strlen(p));
	md5_finish(&state, digest);

	for (di = 0; di < 16; di++)
	{
		sprintf(hex_output + di * 2, "%02x", digest[di]);
	}  
	return hex_output;
}

int Game::serveurConnection()
{
	int sock;
	string NomDistant = this->adresseServeurPrincipal;
	struct sockaddr_in adresse;
	struct hostent *recup;

	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		perror("Erreur ouverture");
		return(-1);
	}

	if ((recup = gethostbyname(NomDistant.c_str())) == NULL)
	{
		perror("Erreur obtention adresse");
		return(-1);
	}
	memcpy((char *)&adresse.sin_addr, (char *)recup->h_addr, recup->h_length);

	adresse.sin_family = AF_INET;
	adresse.sin_port = htons((u_short)this->portEcouteServeurPrincipal);

	if (connect(sock, (struct sockaddr *)&adresse, sizeof(adresse)) == -1)
	{
		perror("Erreur connexion");
		return(-1);
	}

	char * msg = (char *)malloc(11);
	strcpy(msg, "gameServer");
	char * mesgCrypte = (char *)malloc(33);
	mesgCrypte = md5_str(msg);

	if (write(sock, mesgCrypte, 33) == -1)
	{
		perror("Erreur ecriture");
		return(-1);
	}

	free(msg);
	free(mesgCrypte);

	return 0;
}

int * Game::connectionWaiting()
{
	this->sockEcoute = this->createTCPSock(this->portEcoute);
	listen(this->sockEcoute, 10);

	while(1)
	{ 
		pthread_mutex_lock(&mutexConnectionClient);
		this->msg_sock = accept(this->sockEcoute, NULL, NULL);
		if (this->msg_sock == -1)
		{
			perror("Erreur accept");
		}
		else
		{
			this->log("Connexion nouveau client sur le port 2331");	
		}
		this->traiteNouveauClient();
		pthread_mutex_unlock(&mutexConnectionClient);
	}
	return 0;
}

void  * Game::traiterClient() 
{
	pthread_mutex_lock(&mutex_msg_sockConnectionClient);
	int socket = this->msg_sock;
	pthread_mutex_unlock(&mutex_msg_sockConnectionClient);

	char message[1024];

	struct sockaddr_in from;
	unsigned int from_len= sizeof(from); 
	getpeername(socket, (struct sockaddr*)&from, &from_len);

	printf("Connexion avec %s : %d\n", inet_ntoa(from.sin_addr), ntohs(from.sin_port)); 

	if (read(socket, message, 1023) == -1)
	{
		perror("Problemes");
		exit(-1);
	}

	cout << endl;
	cout << "serveur de partie recoi : " << message << endl;

	// decoupage	
	istringstream iss( message );
	string mot, tab[3];
	int i=0;
	while ( std::getline(iss, mot, ':' ) )
	{	
		tab[i] = mot;
		i++;
	}

	// test si le jeton est correct
	if ((strcmp(tab[0].c_str(), this->jeton.c_str()) == 0) 
			and (strcmp(tab[0].c_str(), "jeton") != 0))
	{
		// si correct, on ajoute le joueur a la liste des joueurs avec ces coordonnes initiale, son identifiant, etc.
		int x = 1, y = 1;
		if ( this->addPlayer(Joueur(tab[2], (this->numberPlayer + 1),
						inet_ntoa(from.sin_addr), tab[1], 1, 1)
						) == -1)
		{
			// echec de l'ajout
			printf("Erreur lors de l'ajout du joueur %s\n", tab[2].c_str());
			this->log("Erreur lors de l'ajout du joueur.");
			return NULL;
		}

		else
		{
			// le joueur est bien ajoute
			printf("%s (identifiant : %d, adresse : %s) a rejoint la partie\n",
							tab[2].c_str(), (this->numberPlayer), inet_ntoa(from.sin_addr));
			this->log(tab[2]+" a rejoint la partie.");

			// on averti les autres joueurs
			// format du message :
			//    NEW:id_j:type:l:c
			//char identifiant[2];
			//char coordX[2];
			//char coordY[2];
			char * identifiant = (char *)malloc(1024);
			char * coordX = (char *)malloc(1024);
			char * coordY = (char *)malloc(1024);

			sprintf(identifiant, "%d", (this->numberPlayer));
			sprintf(coordX, "%d", x);
			sprintf(coordY, "%d", y);
			strcpy(message, "NEW");
			strcat(message, ":");
			strcat(message, identifiant);
			strcat(message, ":");
			strcat(message, tab[2].c_str());
			strcat(message, ":");
			strcat(message, tab[1].c_str());
			strcat(message, ":");
			strcat(message, coordX);
			strcat(message, ":");
			strcat(message, coordY);
			strcat(message, ":");
			this->sendOthers(from, message);




			// construction du message a renvoyer au nouveau client
			// format du message :
			//   id_j*@*l*,*c*@*id_j1*:*nom_j1*:*t_j1*:*l_j1*,*c_j1*;*id_j2*:*nom_j2*:*t_j2*:*l_j2*,*c_j2
			memset (message, 0, sizeof (message));
			strcpy(message, "|");
			strcat(message, "ok");
			strcat(message, "|");
			strcat(message, identifiant);
			strcat(message, "@");
			strcat(message, coordX);
			strcat(message, ",");
			strcat(message, coordY);
			strcat(message, "@");

			list<Joueur>::iterator i;
			for(i = gamerList.begin(); i != gamerList.end(); ++i)
			{
				if (i->getID() != (this->numberPlayer))
				{
					sprintf(identifiant, "%d", i->getID());
					sprintf(coordX, "%d", i->getX());
					sprintf(coordY, "%d", i->getY());

					strcat(message, identifiant);
					strcat(message, ":");
					strcat(message, i->getNickname().c_str());
					strcat(message, ":");
					strcat(message, i->getType().c_str());
					strcat(message, ":");
					strcat(message, coordX);
					strcat(message, ",");
					strcat(message, coordY);
					strcat(message, ";");

					memset (identifiant, 0, sizeof (identifiant));
					memset (coordX, 0, sizeof (coordX));
					memset (coordY, 0, sizeof (coordY));
				}
			}
			strcat(message, "|");
			
			free(identifiant);
			free(coordX);
			free(coordY);
		}

	}
	else
	{
		printf("Authentification de (%s, %s) echouee. Jeton invalide\n",
			tab[2].c_str(), inet_ntoa(from.sin_addr));
		this->log("Authentification echouee. Jeton invalide.");
		strcpy(message, "mauvaisJeton");
	}

	cout << "serveur de partie renvoi : " << message << endl;
	// envoie de la reponse au nouveau client 
	if (write(socket, message, sizeof(message)) == -1 )
	{
		perror("Erreur write");
	}
	
	close(socket);
	return NULL;
}

void * Game::receive()
{
	unsigned int tailleAdresse;
	struct sockaddr_in adresseDistante;

	while(1)
	{
		//pthread_mutex_lock(&mutexReceive);
		tailleAdresse = sizeof(struct sockaddr_in);

		char * message = (char *)malloc(1024);

		if(recvfrom(this->sockLecture, message, 1024, 0, (struct sockaddr*)&adresseDistante, &tailleAdresse) == -1)
		{
			perror("Erreur de lecture sur la socket");
			exit(-1);
		}

		fprintf(stdout, "Message de (%s : %d) : %s\n", inet_ntoa(adresseDistante.sin_addr), ntohs(adresseDistante.sin_port), message);

		cout << endl;
		cout << "Serveur de partie recoi : " << message << endl;

		// decoupe
		istringstream iss( message );
		string mot, tab[5];
		int j=0;
		while ( std::getline(iss, mot, ':' ) )
		{	
			tab[j] = mot;
			j++;
		}

		// tests		
		if (strcmp(tab[0].c_str(), "MVT") == 0)
		{
			// construction du message a renvoyer au client
			// format du message :
			//   MVT|identifiant|x,y
			memset (message, 0, sizeof (message));
			strcpy(message, "MVT");

			// parcours de la liste des joueurs
			list<Joueur>::iterator i;
			for(i = gamerList.begin(); i != gamerList.end(); ++i)
			{
				if (i->getIP() == inet_ntoa(adresseDistante.sin_addr))
				{
					i->setX(atoi(tab[2].c_str()));
					i->setY(atoi(tab[3].c_str()));

					char identifiant[2];
					sprintf(identifiant, "%d", i->getID());
					strcat(message, ":");
					strcat(message, identifiant);
					strcat(message, ":");
					strcat(message, tab[2].c_str());
					strcat(message, ":");
					strcat(message, tab[3].c_str());
					strcat(message, ":");
					break;
				}
			}
		}

		else if (strcmp(tab[0].c_str(), "DEC") == 0)
		{
			this->removePlayer(inet_ntoa(adresseDistante.sin_addr));

			list<Joueur>::iterator i;
			for(i = gamerList.begin(); i != gamerList.end(); ++i)
			{				
				if (i->getID() == atoi(tab[1].c_str()))
				{
					printf("\n%s a quitte la partie\n", i->getNickname().c_str());
				}
			}
			memset (message, 0, sizeof (message));
			strcpy(message, "DEC:");
			strcat(message, tab[1].c_str());
			strcat(message, ":");
		}
		
		else if (strcmp(message, "AreYouAlive?") == 0)
		{
			this->sendALive();
		}

		this->sendToAll(adresseDistante, message);
		free(message);
		//pthread_mutex_unlock(&mutexReceive);
	}
	close(this->sockEcoute);
	exit(0);
}

int Game::sendOthers(struct sockaddr_in adresseDistante, string reponse)
{
	pthread_mutex_lock(&mutexSend);

	struct sockaddr_in adresse;
	struct hostent *recup;
	string nomDistant, nickname, type;

	// parcours de la liste des joueurs
	list<Joueur>::iterator i;
	for(i = gamerList.begin(); i != gamerList.end(); ++i)
	{
		// si différent de l'éméteur, envoie des données.
		if (i->getIP() != inet_ntoa(adresseDistante.sin_addr))
		{
			// récupération des informations sur le destinataire actuel
			nomDistant = i->getIP();
			nickname = i->getNickname();
			type = i->getType();

			if ((this->sockEcriture = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
			{
				perror("Erreur ouverture");
				pthread_mutex_unlock(&mutexSend);
				return(-1);
			}

			if ((recup = gethostbyname(nomDistant.c_str())) == NULL)
			{
				perror("Erreur obtention adresse");
				pthread_mutex_unlock(&mutexSend);
				return(-1);
			}

			memcpy((char *)&adresse.sin_addr, (char *)recup->h_addr, recup->h_length);

			adresse.sin_family = AF_INET;
			adresse.sin_port = htons((u_short)this->portEcouteDistant);

			if (connect(this->sockEcriture, (struct sockaddr *)&adresse, sizeof(adresse)) == -1)
			{
				perror("Erreur connect");
				pthread_mutex_unlock(&mutexSend);
				return(-1);
			}

			if ((sendto(this->sockEcriture, reponse.c_str(), strlen(reponse.c_str()), 0, NULL, 0)) == -1)
			{
				perror("Erreur ecriture");
				pthread_mutex_unlock(&mutexSend);
				return(-1);
			}
			close(this->sockEcriture);
		}
	}
	pthread_mutex_unlock(&mutexSend);
	return 0;
}

int * Game::receiveServeur()
{
	while(1)
	{ 
		this->msg_sockServeur = accept(this->sockEcouteServeur, NULL, NULL);
		if (this->msg_sockServeur == -1)
		{
			perror("Erreur accept");
		}
		this->initTraitementServeur();
	}
	close(this->msg_sockServeur);
	return 0;
}

void Game::traiteServeur()
{
	char message [1024];
	int sock = this->msg_sockServeur;

	if (read(sock, message, 1023) == -1)
	{
		perror("Problemes");
		exit(-1);
	}
	printf("Message recu du serveur principal : %s\n", message);


	// decoupe
	int i = 0;
	string tab[2], mot;
	istringstream iss( message );
	while ( std::getline(iss, mot, ':' ) )
	{
		tab[i] = mot; 
		i++;
	}

	if (strcmp(message, "succes") == 0)
	{
		printf("Connection accepte\n");
		return;
	}

	else if (strcmp(message, "echec") == 0)
	{
		// connexion refusee, on quitte le programme
		printf("Connection refuse\n");
		exit(0);
	}

	else if (strcmp(message, "sendMeInfo") == 0)
	{	// on prepare les donnees a envoyer au serveur principal
		// format:
		//  MyInfo:nbPlayer:nbPacman:nbFantome
		char joueur [2];
		sprintf (joueur, "%d", this->numberPlayer);

		char pacman [2];
		sprintf (pacman, "%d", this->numberPacman);

		char fantome [2];
		sprintf (fantome, "%d", this->numberGhost);

		strcpy(message, "MyInfo:");
		strcat(message, joueur);
		strcat(message, ":");
		strcat(message, pacman);
		strcat(message, ":");
		strcat(message, fantome);
	}

	else if (strcmp(tab[0].c_str(), "jeton") == 0)
	{
		// initialisation du nouveau jeton
		this->jeton = tab[1];
		printf("Nouveau jeton initialise\n");
		return;
	}

	else if (strcmp(message, "AreYouAlive?") == 0)
	{
		// test de présence
		strcpy(message, "ImAlive");
	}
	else
		// on envoie pas de message d'erreurs. on passe silencieusement...
		return;

	// envoie de la réponse
	int portServeur = 7772;
	string NomDistant = this->adresseServeurPrincipal;
	struct sockaddr_in adresse;
	struct hostent *recup;

	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		perror("Erreur ouverture");
	}

	if ((recup = gethostbyname(NomDistant.c_str())) == NULL)
	{
		perror("Erreur obtention adresse");
	}
	memcpy((char *)&adresse.sin_addr, (char *)recup->h_addr, recup->h_length);

	adresse.sin_family = AF_INET;
	adresse.sin_port = htons((u_short)portServeur);
	if (connect(sock, (struct sockaddr *)&adresse, sizeof(adresse)) == -1)
	{
		perror("Erreur connexion");
	}

	if (write(sock, message, strlen(message)) == -1)
	{
		perror("Erreur ecriture");
	}
	close(sock);
}

int Game::sendToAll(struct sockaddr_in adresseDistante, string reponse)
{
	struct sockaddr_in adresse;
	struct hostent *recup;
	string nomDistant, nickname, type;

	// parcours de la liste des joueurs
	list<Joueur>::iterator i;
	for(i = gamerList.begin(); i != gamerList.end(); ++i)
	{
			// récupération des informations sur le destinataire actuel
			nomDistant = i->getIP();
			nickname = i->getNickname();
			type = i->getType();

			if ((this->sockEcriture = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
			{
				perror("Erreur ouverture");
				return(-1);
			}

			if ((recup = gethostbyname(nomDistant.c_str())) == NULL)
			{
				perror("Erreur obtention adresse");
				return(-1);
			}

			memcpy((char *)&adresse.sin_addr, (char *)recup->h_addr, recup->h_length);

			adresse.sin_family = AF_INET;
			adresse.sin_port = htons((u_short)this->portEcouteDistant);

			if (connect(this->sockEcriture, (struct sockaddr *)&adresse, sizeof(adresse)) == -1)
			{
				perror("Erreur connect");
				return(-1);
			}

			cout << "Serveur de partie renvoi :" << reponse << " a : " << i->getIP() << endl;
			if ((sendto(this->sockEcriture, reponse.c_str(), strlen(reponse.c_str()), 0, NULL, 0)) == -1)
			{
				perror("Erreur ecriture");
				return(-1);
			}
			close(this->sockEcriture);

	}
	reponse.clear();
	return 0;
}

int Game::sendALive()
{
	// envoie de la réponse
	int portServeur = 7772;
	int sock;
	string NomDistant = this->adresseServeurPrincipal;
	struct sockaddr_in adresse;
	struct hostent *recup;

	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		perror("Erreur ouverture");
	}

	if ((recup = gethostbyname(NomDistant.c_str())) == NULL)
	{
		perror("Erreur obtention adresse");
	}
	memcpy((char *)&adresse.sin_addr, (char *)recup->h_addr, recup->h_length);

	adresse.sin_family = AF_INET;
	adresse.sin_port = htons((u_short)portServeur);
	if (connect(sock, (struct sockaddr *)&adresse, sizeof(adresse)) == -1)
	{
		perror("Erreur connexion");
	}

	if (write(sock, "ImAlive", strlen("ImAlive")) == -1)
	{
		perror("Erreur ecriture");
	}
	close(sock);
	return 0;
}

int Game::createTCPSock(u_short port)
{
	int sock, retour;
	struct sockaddr_in adresse;

	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock<0)
	{
		perror ("Erreur ouverture");
		return(-1);
	}

	adresse.sin_family = AF_INET;
	adresse.sin_port = htons((u_short)port);
	adresse.sin_addr.s_addr = INADDR_ANY;

	retour = bind (sock, (struct sockaddr *)&adresse, sizeof(adresse));

	if (retour<0)
	{
		perror ("Impossible de nommer le socket");
		return(-1);
	}
	return (sock);
}

int Game::createUDPSock(u_short port)
{
	int sock, retour;
	struct sockaddr_in adresseDistante;

	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock<0)
	{
		perror ("Erreur ouverture");
		return(-1);
	}

	adresseDistante.sin_family = AF_INET;
	adresseDistante.sin_port = htons((u_short)port);
	adresseDistante.sin_addr.s_addr = INADDR_ANY;

	retour = bind (sock, (struct sockaddr *)&adresseDistante, sizeof(adresseDistante));

	if (retour<0)
	{
		perror ("Impossible de nommer le socket");
		return(-1);
	}
	return (sock);
}

int Game::addPlayer(Joueur joueur)
{
	pthread_mutex_lock(&mutexAjoutClient);
	if (this->numberPlayer < 10)
	{
		this->gamerList.push_back(joueur);
		(joueur.getType() == "p")? this->numberPacman++ : this->numberGhost++;
		this->numberPlayer++;
		pthread_mutex_unlock(&mutexAjoutClient);
		return 0;
		
	}
	pthread_mutex_unlock(&mutexAjoutClient);
	return -1;
}

int Game::removePlayer(string adresse)
{
	pthread_mutex_lock(&mutexSupprimeClient);
	list<Joueur>::iterator i;
	for(i = gamerList.begin(); i != gamerList.end(); ++i)
	{
		if (i->getIP() == adresse)
		{
			(i->getType() == "p")? this->numberPacman-- : this->numberGhost--;
			this->gamerList.erase(i);
			this->numberPlayer--;
			pthread_mutex_unlock(&mutexSupprimeClient);
			return 0;
		}
	}
	pthread_mutex_unlock(&mutexSupprimeClient);
	return 0;
}

void Game::log(string message)
{
	// récupère l'heure système
	time_t timer1;
	time(&timer1);
	int secondes, minutes, heures;
	string fichier = "log";
	struct tm *newTime1;
	newTime1 = localtime(&timer1);
	heures = newTime1->tm_hour;
	minutes = newTime1->tm_min;
	secondes = newTime1->tm_sec;

	ofstream file;

	file.open(fichier.c_str(), ios::out | ios::ate | ios::app);

	if (file.bad())
	{
		cerr << "Fichier [" << fichier << "] invalide." << endl;
		exit(-1);
	}
	if (file.is_open())
	{
        // Écrit les données :
		file << "[" << heures << ":" << minutes << ":" << secondes << "] " << message << endl;

		file.close();
	}
}
